//Header files
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <libspe2.h>
#include <cbe_mfc.h>
#include <pthread.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include "common.h"

//Declaring matrices
unsigned int matrixA[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixB[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixC[MATRIX_SIZE][MATRIX_SIZE]  QWORD_ALIGNED;
unsigned int matrixA2[THREAD_NUM-1][ROW_NUM][MATRIX_SIZE] QWORD_ALIGNED;
unsigned int matrixALast[ROW_NUM+OVFL_ROWS_NUM][MATRIX_SIZE] QWORD_ALIGNED;
		
CONTROL_BLOCK cb[THREAD_NUM] QWORD_ALIGNED;
extern spe_program_handle_t spu;

//Data structure for running SPE thread
typedef struct ppu_pthread_data {
	spe_context_ptr_t speid;
  	pthread_t pthread;
  	void *argp;
  	int tid QWORD_ALIGNED;
} ppu_pthread_data_t;

//Declaring struct
ppu_pthread_data_t data[THREAD_NUM];

// create and run one SPE thread
void *ppu_pthread_function(void *arg) {
  	ppu_pthread_data_t *datap = (ppu_pthread_data_t *)arg;
  	int rc;
  	unsigned int entry = SPE_DEFAULT_ENTRY;

	if ((rc = spe_context_run(datap->speid, &entry, 0, datap->argp,(void *) datap->tid, NULL)) < 0) {
		fprintf (stderr, "Failed spe_context_run(rc=%d, errno=%d, strerror=%s)\n", rc, errno, strerror(errno));
		exit (1);
	}

  	pthread_exit(NULL);
}

int main() {

	//Declarations
  	int rc;
  	int i,j,k;
	srand( time(NULL) );

  	//Generating matrix elements
  	for(i=0; i<MATRIX_SIZE; i++)
  	{
		for(j=0; j<MATRIX_SIZE; j++)
		{
			matrixA[i][j] = (int) rand()%10;
			matrixB[i][j] = (int) rand()%10;
		}
	}

	for(i=0;i<THREAD_NUM;i++)
	{
		// give each thread an ID
		data[i].tid = i;
		
		// create SPE context and load SPE program into the SPE context
		if ((data[i].speid = spe_context_create (0, NULL)) == NULL)
		{
			fprintf (stderr, "Failed spe_context_create(errno=%d strerror=%s)\n", errno, strerror(errno));
			exit (3);
		}
  
		//Load program
		if ((rc = spe_program_load (data[i].speid, &spu)) != 0)
		{
			fprintf (stderr, "Failed spe_program_load(errno=%d strerror=%s)\n", errno, strerror(errno));
			exit (3);
		}
	}
  
	//This part is used to Data matrixInPPE for spu.c in only one SPE
	for(i=0;i<THREAD_NUM-1;i++)
	{
		//Storing proper portion of matrixA to be used in the matrix multiplication
		for(j=0;j<ROW_NUM;j++)
		{
			for(k=0;k<MATRIX_SIZE;k++)
			{
				matrixA2[i][j][k] = matrixA[j+data[i].tid*ROW_NUM][k];
			}
		}

  		// Fill in control block with matrixA data
  		cb[i].a = (unsigned int)&matrixA2[i][0][0];
  		cb[i].b = (unsigned int)matrixB;
  		cb[i].c = (unsigned int)&matrixC[data[i].tid*ROW_NUM][0];

		data[i].argp = (unsigned long long *) &cb[i];
	}

	for(i=THREAD_NUM-1;i<THREAD_NUM;i++)
	{
		//Storing proper portion of matrixB to be used in the matrix multiplication
		for(j=0;j<ROW_NUM+OVFL_ROWS_NUM;j++)
		{
			for(k=0;k<MATRIX_SIZE;k++)
			{
				matrixALast[j][k] = matrixA[j+data[i].tid*ROW_NUM][k];
			}
		}

  		// Fill in control block with matrixB data
  		cb[i].a = (unsigned int)matrixALast;
  		cb[i].b = (unsigned int)matrixB;
  		cb[i].c = (unsigned int)&matrixC[data[i].tid*ROW_NUM][0];

		data[i].argp = (unsigned long long *) &cb[i];
	}

	//Start timing
	#ifdef TIMING  
		long unsigned int start_t, end_t;
		start_t=__mftb();
	#endif

	//creating threads
	for(i=0;i<THREAD_NUM;i++)
	{  
		// create SPE pthreads
		if ((rc = pthread_create (&data[i].pthread, NULL, &ppu_pthread_function, &data[i])) != 0)
		{
			fprintf (stderr, "Failed pthread_create(errno=%d strerror=%s)\n", errno, strerror(errno));
			exit (3);
		}      
  
		// wait for SPE to complete
		if ((rc = pthread_join (data[i].pthread, NULL)) != 0)
		{
			fprintf (stderr, "Failed pthread_join(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
			exit (1);
		}
	}

	//Ending timing
	#ifdef TIMING  
	//compute the timein us(10E-6)       
	end_t=__mftb();
	#endif


	for(i=0;i<THREAD_NUM;i++)
	{
		// destroy the SPE contexts	
		if ((rc = spe_context_destroy (data[i].speid)) != 0)
		{
			fprintf (stderr, "Failed spe_context_destroy(rc=%d, errno=%d strerror=%s)\n", rc, errno, strerror(errno));
			exit (1);
		}
	}

	//Printing out result of AB
	for(i=0;i<MATRIX_SIZE;i++)
	{
		for(j=0;j<MATRIX_SIZE;j++)
		{
			//if(i==0)
			printf("%d ", matrixC[i][j]);
		}
		printf("\n");
	}

	//Display time it took to run
	printf("\n\n\nTotal time used by SPE: %f us\n\n\n", (float)((end_t-start_t)/79.8)); 
  
  return 0;
}
